package server

import (
	"database/sql"
	"errors"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"runtime"
	"strconv"
	"strings"
	"time"
)

type ReturnCode int

const (
	CodeOK             ReturnCode = 0  //正常
	CodeSvrERROR       ReturnCode = -1 //服务器返回异常
	CodeParamError     ReturnCode = -2 //参数错误
	CodeDatabaseError  ReturnCode = -3 //数据库访问异常
	CodeSessionError   ReturnCode = -4 //session错误
	CodeSessionExpired ReturnCode = -5 //session过期
	CodeHttpError      ReturnCode = -6 //http相关错误
	CodeCollapse       ReturnCode = -7 //程序崩溃
)

type ReturnStruct struct {
	Code    ReturnCode  `json:"code"`
	Data    interface{} `json:"data"`
	Message string      `json:"msg"`
}

func (ret *ReturnStruct) is_success() bool {
	return ret.Code == CodeOK
}

func (ret *ReturnStruct) is_fail() bool {
	return ret.Code != CodeOK
}

//成功返回
func SuccRet(data interface{}) *ReturnStruct {
	ret := new(ReturnStruct)
	ret.Code = CodeOK
	ret.Data = data
	return ret
}

//错误返回
func FailRet(code ReturnCode, info interface{}) *ReturnStruct {
	ret := new(ReturnStruct)
	ret.Code = code
	ret.Message = fmt.Sprintf("%+v", info)
	return ret
}

// time时区
var TimeLocal *time.Location

//随机数
var GlobalRand *rand.Rand

//小程序参数
const WXAPPId string = "wx8524c080101b48ed"
const WXAPPSecret string = "74cb1422b075ec17796d2b49251ff52f"

//session过期天数
const SessionExpiredDays = 30

var GlobalConf *GlobalConfig

func SetGlobalConfig(cfg *GlobalConfig) {
	GlobalConf = cfg
}

//初始化数据库连接池
func InitDatabaseConnection() (*sqlx.DB, error) {
	//数据库连接池
	var db *sqlx.DB

	//连接数据库. Db是所有连接的管理者， 连接断开后会自动重连。
	database, err := sqlx.Open("mysql", "root:"+GlobalConf.DbSecret+"@tcp(127.0.0.1:3306)/test_db")
	if err != nil {
		return nil, errors.New("连接数据库失败: " + err.Error())
	}
	db = database
	db.SetMaxOpenConns(200)
	db.SetMaxIdleConns(50)

	//设置db析构的时候关闭
	runtime.SetFinalizer(db, func(obj *sqlx.DB) {
		obj.Close()
	})
	return db, nil
}

func init() {
	var err error
	TimeLocal, err = time.LoadLocation("Asia/Shanghai")
	if err != nil {
		fmt.Println("get current time location failed, ", err)
		return
	}

	GlobalRand = rand.New(rand.NewSource(time.Now().Unix()))
}

func Uint8ToString(bs []uint8) string {
	ba := []byte{}
	for _, b := range bs {
		ba = append(ba, byte(b))
	}
	return string(ba)
}

//获取今天格式：2006-01-02
func TodayStr() string {
	return time.Now().Format("2006-01-02")
}

func GetBeginningOfToday() time.Time {
	str := time.Now().Format("2006-01-02")
	timeobj, err := time.ParseInLocation("2006-01-02", str, TimeLocal)
	if err != nil {
		panic("get beginning of today failed: " + err.Error())
	}
	return timeobj
}

//解析数据库的日期字段
func ParseDBDate(bs []uint8) (time.Time, error) {
	var timeobj time.Time
	if bs == nil {
		return timeobj, errors.New("bs is nil")
	}
	str := Uint8ToString(bs)
	timeobj, err := time.ParseInLocation("2006-01-02", str, TimeLocal)
	if err != nil {
		return timeobj, err
	}
	return timeobj, nil
}

// RandString 生成随机字符串
func RandString(length int) string {
	bytes := make([]byte, length)
	for i := 0; i < length; i++ {
		bytes[i] = randStringBase[GlobalRand.Intn(62)]
	}
	return string(bytes)
}

var randStringBase []byte = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWSYZ0123456789")

// http get
func HttpGet(api_url string, params map[string]string) (retstring []byte, err error) {
	apiUrl := api_url
	// URL param
	data := url.Values{}
	for k, v := range params {
		data.Set(k, v)
	}

	u, err := url.ParseRequestURI(apiUrl)
	if err != nil {
		return nil, errors.New("parse url failed: " + err.Error())
	}
	u.RawQuery = data.Encode() // URL encode
	resp, err := http.Get(u.String())
	if err != nil {
		return nil, errors.New("http get error: " + err.Error())
	}
	defer resp.Body.Close()

	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, errors.New("get resp failed: " + err.Error())
	}
	return b, nil
}

//判断session是否过期
func IsSessionExpired(sessionCreateDate time.Time) bool {
	dura := GetBeginningOfToday().Sub(sessionCreateDate).Hours() / 24
	return dura > SessionExpiredDays
}

//参考： https://blog.csdn.net/weimingjue/article/details/91042649
func SelectResultFromRows(rows *sql.Rows) ([]map[string]interface{}, error) {
	columns, err := rows.Columns()
	if err != nil {
		return nil, err
	}
	columnLength := len(columns)
	if 0 == columnLength {
		return nil, errors.New("no columns!")
	}
	cache := make([]interface{}, columnLength) //临时存储每行数据
	for index, _ := range cache {              //为每一列初始化一个指针
		var a interface{}
		cache[index] = &a
	}
	var list []map[string]interface{} //返回的切片
	for rows.Next() {
		_ = rows.Scan(cache...)

		item := make(map[string]interface{})
		for i, data := range cache {
			item[columns[i]] = *data.(*interface{}) //取实际类型

			//将[]byte转化为string
			bytesArr, ok := item[columns[i]].([]byte)
			if ok {
				item[columns[i]] = string(bytesArr)
			}
		}
		list = append(list, item)
	}
	return list, nil
}

//从SelectResultFromRows的结果中取一列。可取int 或 int64
func GetIntColumnFromResult(result []map[string]interface{}, col string) ([]int64, error) {
	arr := make([]int64, 0)
	for i := 0; i < len(result); i++ {
		val, ok := result[i][col]
		if !ok {
			return nil, errors.New(fmt.Sprintf("GetIntColumnFromResult: 某一行的键不存在 %s", col))
		}
		intVal, ok := val.(int)
		int64Val, ok2 := val.(int64)
		if !ok && !ok2 {
			return nil, errors.New(fmt.Sprintf("GetIntColumnFromResult: 某一行的键不能转化 %s", col))
		}
		if ok {
			arr = append(arr, int64(intVal))
		} else {
			arr = append(arr, int64Val)
		}
	}
	return arr, nil
}
func GetStringColumnFromResult(result []map[string]interface{}, col string) ([]string, error) {
	arr := make([]string, 0)
	for i := 0; i < len(result); i++ {
		val, ok := result[i][col]
		if !ok {
			return nil, errors.New(fmt.Sprintf("GetStringColumnFromResult: 某一行的键不存在 %s", col))
		}
		strVal, ok := val.(string)
		if !ok {
			return nil, errors.New(fmt.Sprintf("GetStringColumnFromResult: 某一行的键不能转化 %s", col))
		}
		arr = append(arr, strVal)
	}
	return arr, nil
}

//在错误前面增加前缀，并返回新的错误。
func PrefixError(prefix string, err error) error {
	return errors.New(prefix + ": " + err.Error())
}

//封装数据库错误返回
func DBErrReturn(info string, err error) *ReturnStruct {
	return FailRet(CodeDatabaseError, PrefixError(info, err))
}

//封装服务器普通错误返回
func SvrErrReturn(info string) *ReturnStruct {
	return FailRet(CodeSvrERROR, errors.New(info))
}

//封装参数错误返回
func ParamErrReturn(info string) *ReturnStruct {
	return FailRet(CodeParamError, errors.New(info))
}

//整数数组转字符串数组
func ArrIntToArrStr(lst []int) []string {
	result := make([]string, 0)
	for i := 0; i < len(lst); i++ {
		result = append(result, strconv.FormatInt(int64(lst[i]), 10))
	}
	return result
}

func ArrInt64ToArrStr(lst []int64) []string {
	result := make([]string, 0)
	for i := 0; i < len(lst); i++ {
		result = append(result, strconv.FormatInt(lst[i], 10))
	}
	return result
}

//字符串数组转整数数组
func ArrStrToArrInt64(lst []string) ([]int64, error) {
	result := make([]int64, 0)
	for i := 0; i < len(lst); i++ {
		num, err := strconv.ParseInt(lst[i], 10, 64)
		if err != nil {
			return nil, errors.New(fmt.Sprintf("ArrStrToArrInt64 parse (%s) error!", lst[i]))
		}
		result = append(result, num)
	}
	return result, nil
}

//切片去重
func RemoveDupInt(arr []int) []int {
	m := make(map[int]struct{})
	for i := 0; i < len(arr); i++ {
		m[arr[i]] = struct{}{}
	}

	result := make([]int, 0, len(m))
	for val := range m {
		result = append(result, val)
	}
	return result
}

func RemoveDupInt64(arr []int64) []int64 {
	m := make(map[int64]struct{})
	for i := 0; i < len(arr); i++ {
		m[arr[i]] = struct{}{}
	}

	result := make([]int64, 0, len(m))
	for val := range m {
		result = append(result, val)
	}
	return result
}

func RemoveDupString(arr []string) []string {
	m := make(map[string]struct{})
	for i := 0; i < len(arr); i++ {
		m[arr[i]] = struct{}{}
	}

	result := make([]string, 0, len(m))
	for val := range m {
		result = append(result, val)
	}
	return result
}

func GetFileNameFromUrl(url string) string {
	parts := strings.Split(url, "/")
	if len(parts) == 0 {
		return ""
	} else {
		return parts[len(parts)-1]
	}
}

func MinInt(args ...int) int {
	if len(args) == 0 {
		return 0
	}
	min := args[0]
	for _, n := range args {
		if min > n {
			min = n
		}
	}
	return min
}

func MinInt64(args ...int64) int64 {
	if len(args) == 0 {
		return 0
	}
	min := args[0]
	for _, n := range args {
		if min > n {
			min = n
		}
	}
	return min
}

func MaxInt(args ...int) int {
	if len(args) == 0 {
		return 0
	}
	max := args[0]
	for _, n := range args {
		if max < n {
			max = n
		}
	}
	return max
}

func MaxInt64(args ...int64) int64 {
	if len(args) == 0 {
		return 0
	}
	max := args[0]
	for _, n := range args {
		if max < n {
			max = n
		}
	}
	return max
}

func ArrContainInt(lst []int, val int) bool {
	for _, v := range lst {
		if v == val {
			return true
		}
	}
	return false
}

func ArrContainInt64(lst []int64, val int64) bool {
	for _, v := range lst {
		if v == val {
			return true
		}
	}
	return false
}

//删除数组元素，返回新数组
func ArrRemoveInt(lst []int, val int) []int {
	arr := make([]int, 0)
	for _, v := range lst {
		if v != val {
			arr = append(arr, v)
		}
	}
	return arr
}
func ArrRemoveInt64(lst []int64, val int64) []int64 {
	arr := make([]int64, 0)
	for _, v := range lst {
		if v != val {
			arr = append(arr, v)
		}
	}
	return arr
}
func ArrRemoveIntString(lst []string, val string) []string {
	arr := make([]string, 0)
	for _, v := range lst {
		if v != val {
			arr = append(arr, v)
		}
	}
	return arr
}

//获取字符串中文长度
func ChnLen(str string) int {
	return len([]rune(str))
}
